{*******************************************************}
{                                                       }
{       IBAN                                            }
{                                                       }
{       Copyright (C) 2011 Heiko Adams                  }
{                                                       }
{*******************************************************}

{
     The contents of this file are subject to the Mozilla Public License
     Version 1.1 (the "License"); you may not use this file except in
     compliance with the License. You may obtain a copy of the License at
     http://www.mozilla.org/MPL/

     Software distributed under the License is distributed on an "AS IS"
     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
     License for the specific language governing rights and limitations
     under the License.

     The Original Code is IBAN.pas.

     The Initial Developer of the Original Code is Heiko Adams <heiko.adams@gmail.com>.

     Contributor(s): ______________________________________.
}

unit IBAN;

interface

uses IBANMetrics;

type
	TIBAN = class
	private
  	FKTO: string;
	  FBLZ: string;
  	FLand: string;
    FIBAN: string;

    FMetrics: TIBANMetrics;

    procedure SetLand(const aValue: string);
    function CodiereLandIBAN(const aLand: string): string;
    function Modulo97PruefZiffer(const aIBAN:string):Integer;
    function PruefeIBAN: Boolean;
    function BerechneIBAN: string;
    function GetLand: string;
    function GetCountryFromIBAN: string;
    procedure SetIBAN(const aValue: string);
  public
    property Konto: string read FKTO write FKTO;
    property BLZ: string read FBLZ write FBLZ;
    property Land: string read GetLand write SetLand;
    property IBAN: string read BerechneIBAN write SetIBAN;
    property Valid: Boolean read PruefeIBAN;

    destructor Destroy; override;
	end;

implementation

uses SysUtils, Windows;

destructor TIBAN.Destroy;
begin
	ZeroMemory(@FMetrics, SizeOf(FMetrics));
  inherited;
end;

procedure TIBAN.SetLand(const aValue: string);
begin
  FLand := Trim(UpperCase(Copy(aValue, 1, 2)));

  if (Length(FLand) < 2) then
	  raise Exception.CreateFmt('Invalid country code: %s', [aValue]);

  ZeroMemory(@FMetrics, SizeOf(FMetrics));
  FMetrics := GetIBANMetrics(FLand);
end;

function TIBAN.GetCountryFromIBAN: string;
begin
	if (Trim(FIBAN) = EmptyStr) then
    raise Exception.Create('IBAN not set');

  Result := Copy(FIBAN, 1, 2);
end;

procedure TIBAN.SetIBAN(const aValue: string);
begin
	if (Trim(aValue) = EmptyStr) then
    raise Exception.Create('No IBAN submitted');

	FIBAN := aValue;
  SetLand(GetCountryFromIBAN);
end;

function TIBAN.GetLand: string;
begin
	if not (FLand = EmptyStr) then
  	Result := FLand
  else if not (FIBAN = EmptyStr) then
  	Result := GetCountryFromIBAN
  else
  	raise Exception.Create('No country or IBAN set');
end;

// Original code by shima (http://www.delphipraxis.net/1061658-post6.html)
function TIBAN.Modulo97PruefZiffer(const aIBAN:string):Integer;
const
   m36:string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var
   nCounter, nPruef : Integer;
begin
   Result := 0;

   for nCounter := 1 to Length(aIBAN) do
   begin
      nPruef := Pos(aIBAN[nCounter], m36) ;

      if (nPruef = 0) then
         raise Exception.CreateFmt('Modulo97PruefZiffer(%s): invalid data', [aIBAN]);

      Dec(nPruef);

      if (nPruef > 9) then
      begin
         Result := Result * 10 + (nPruef div 10);
         nPruef := nPruef mod 10;
      end;

      Result := Result * 10 + nPruef;
      Result := Result mod 97;
   end;
end;

function TIBAN.CodiereLandIBAN(const aLand: string): string;
var
  sLetter: Char;
begin
	if (Length(Trim(aLand)) <> 2) then
    raise Exception.CreateFmt('Invalid country code: %s', [aLand]);

  for sLetter in aLand do
    case sLetter of
      'A': Result := Result + '10';
      'B': Result := Result + '11';
      'C': Result := Result + '12';
      'D': Result := Result + '13';
      'E': Result := Result + '14';
      'F': Result := Result + '15';
      'G': Result := Result + '16';
      'H': Result := Result + '17';
      'I': Result := Result + '18';
      'J': Result := Result + '19';
      'K': Result := Result + '20';
      'L': Result := Result + '21';
      'M': Result := Result + '22';
      'N': Result := Result + '23';
      'O': Result := Result + '24';
      'P': Result := Result + '25';
      'Q': Result := Result + '26';
      'R': Result := Result + '27';
      'S': Result := Result + '28';
      'T': Result := Result + '29';
      'U': Result := Result + '30';
      'V': Result := Result + '31';
      'W': Result := Result + '32';
      'X': Result := Result + '33';
      'Y': Result := Result + '34';
      'Z': Result := Result + '35';
    else
      raise Exception.CreateFmt('Invalid country code: %s', [aLand]);
    end;
end;

function TIBAN.PruefeIBAN(): Boolean;
var
	sBLZ: string;
  sKTO: string;
  sIBAN: string;
  sLand: string;
  sControl: string;
begin
	Result := (Length(FIBAN) = FMetrics.nLenIBAN);

  if Result then
  begin
    sControl := Copy(FIBAN, 3, 2);
    sBLZ := Copy(FIBAN, FMetrics.nStartBLZ, FMetrics.nLenBLZ);
    sKTO := Copy(FIBAN, FMetrics.nStartKTO, FMetrics.nLenKTO);
    sLand := CodiereLandIBAN(GetCountryFromIBAN);
    sIBAN := sBLZ + sKTO + sLand + sControl;
    Result := (Modulo97PruefZiffer(sIBAN) = 1);
  end;
end;

function TIBAN.BerechneIBAN(): string;
var
  sKTO: string;
  sIBAN: string;
  nControl: Integer;
const
	sSuffix = '00';
  nControlBase = 98;
begin
  sKTO := StringOfChar('0', FMetrics.nLenKTO - Length(FKTO)) + FKTO;
  sIBAN := FBLZ + sKTO + CodiereLandIBAN(FLand)+  sSuffix;
  nControl := Modulo97PruefZiffer(sIBAN);
  nControl := nControlBase - nControl;
  FIBAN := FLand + IntToStr(nControl) + FBLZ + sKTO;
  Result := FIBAN;
end;

end.
